﻿namespace MuddyBoots.Greenlight.QC.Common.Patterns.Behaviour.Mediator
{
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Globalization;

    /// <summary>
    /// Maintains a registry of message identification tokens and associated callback method to invoke
    /// </summary>
    public static class Mediator
    {
        /// <summary>
        /// The registry of tokens and associated callback methods
        /// </summary>
        private static readonly ConcurrentDictionary<string, ConcurrentDictionary<string, Action<object>>> Registry = new ConcurrentDictionary<string, ConcurrentDictionary<string, Action<object>>>();

        /// <summary>
        /// Registers the specified token.
        /// </summary>
        /// <param name="token">The token to identify the method.</param>
        /// <param name="instanceId">Name of the instance.</param>
        /// <param name="callback">The callback method.</param>
        /// <exception cref="MediatorException">Failed to register a callback</exception>
        public static void Register(string token, string instanceId, Action<object> callback)
        {
            string key = string.Format(CultureInfo.InvariantCulture, "{0}{1}", callback.Target, instanceId);
            if (!Registry.ContainsKey(token))
            {
                var callbackList = new ConcurrentDictionary<string, Action<object>>();
                if (!callbackList.TryAdd(key, callback))
                {
                    throw new MediatorException(string.Format(CultureInfo.CurrentCulture, "Failed to create callback list for token {0}", token));
                }

                if (!Registry.TryAdd(token, callbackList))
                {
                    throw new MediatorException(string.Format(CultureInfo.CurrentCulture, "Failed to add callback {0} callback list for token {1}", callback.Target, token)); 
                }
            }
            else
            {
                ConcurrentDictionary<string, Action<object>> existingList;
                Registry.TryGetValue(token, out existingList);
                if (existingList != null)
                {
                    existingList.TryAdd(key, callback);
                }
                else
                {
                    throw new MediatorException(string.Format(CultureInfo.CurrentCulture, "Could not add callback {0} to callback list for token {1}", callback.Target, token));
                }
            }
        }

        /// <summary>
        /// Unregisters the specified token.
        /// </summary>
        /// <param name="token">The token.</param>
        /// <param name="instance">The instance.</param>
        /// <param name="callback">The callback.</param>
        // ReSharper disable once UnusedMember.Global
        public static void Unregister(string token, string instanceId, Action<object> callback)
        {
            string key = string.Format(CultureInfo.InvariantCulture, "{0}{1}", callback.Target, instanceId);
            if (Registry.ContainsKey(token))
            {
                Registry[token].TryRemove(key, out callback);
            }
        }

        public static void UnregisterAll(string instanceId)
        {
            foreach(string key in Registry.Keys)
            {
                ConcurrentDictionary<string, Action<object>> listeners = Registry[key];
                foreach (var innerKey in listeners.Keys)
                {
                    var item = listeners[innerKey];
                    Mediator.Unregister(key, instanceId, item);
                }
            }
        }

        /// <summary>
        /// Notifies the colleagues.
        /// </summary>
        /// <param name="token">The token.</param>
        /// <param name="args">The arguments.</param>
        public static void NotifyColleagues(string token, object args)
        {
            if (Registry.ContainsKey(token))
            {
                foreach (var callback in Registry[token])
                {
                    callback.Value(args);
                }
            }
        }
    }
}